<?php

namespace Library\Abstracts;

use Closure;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query;
use Library\OrmManager;


abstract class AbstractRepository
{
    protected EntityManager $manager;
    protected EntityRepository $repository;
    protected string $className;

    private ?QueryBuilder $builder = null;

    public function __construct(string $className = "")
    {
        $ormManager = new OrmManager();
        $this->manager = $ormManager->getManager();
        if ($className){
            $this->className = $className;
        }
        $this->repository = $this->manager->getRepository($this->className);
    }

    /**
     * @return string|null
     * @throws MappingException
     */
    public function getPrimaryKeyFieldName(): ?string
    {
        $metadata = $this->manager->getClassMetadata($this->className);
        if ($metadata->isIdentifierComposite) {
            throw new \RuntimeException(sprintf(
                'Entity "%s" has a composite primary key, which is not supported by this method.',
                $this->className
            ));
        }
        return $metadata->getSingleIdentifierFieldName();
    }



    /**
     * 根据主键查找实体
     *
     * @param mixed $id 主键值
     * @return object|null 找到的实体或 null
     */
    public function find(mixed $id): ?object
    {
        return $this->repository->find($id);
    }

    /**
     * 查找所有实体
     *
     * @return array 所有实体的数组
     */
    public function findAll(): array
    {
        return $this->repository->findAll();
    }

    /**
     * @param string $alias
     * @param ?Closure $closure
     * @return $this
     */
    public function build(string $alias, Closure $closure = null):static
    {
        $this->builder = $this->createQueryBuilder($alias);
        $closure!=null && $closure($this->builder);
        return $this;
    }

    /**
     * @param mixed ...$select
     * @return $this
     */
    public function select(mixed ...$select):static
    {
        $this->builder->select(...$select);
        return $this;
    }

    /**
     * @param mixed ...$predicates
     * @return $this
     */
    public function where(mixed ...$predicates):static
    {
        $this->builder->where(...$predicates);
        return $this;
    }

    /**
     * 设定绑定
     * @param array $params
     * @return $this
     * @throws \Exception
     */
    public function setBindParams(array $params):static
    {
        if (!$this->builder){
            throw new \Exception("No call to method ::Query");
        }
        if ($params){
            foreach ($params as $field=>$value){
                $this->builder->setParameter($field,$value);
            }
        }
        return $this;
    }

    /**
     * @return Query
     */
    public function getQuery():Query
    {
         return $this->builder->getQuery();
    }

    /**
     * @param int $page
     * @param int $pageSize
     * @param bool $joinCollection
     * @return array
     * @throws \Exception
     */
    public function paginator(int $page,int $pageSize, bool $joinCollection = false): array
    {
        if (!$this->builder){
            throw new \Exception("No call to method ::Query");
        }
        $this->builder->setFirstResult(($page - 1) * $pageSize)->setMaxResults($pageSize);
        $paginator = new Paginator($this->getQuery(), $joinCollection);
        $items = $paginator->getIterator();
        $total = $paginator->count();
        $pageCount = ceil($total / $pageSize);
        return [
            "total"=>$total,
            "pageCount"=>$pageCount,
            "items"=>$items
        ];
    }


    /**
     * 根据条件查找实体
     *
     * @param Criteria $criteria 查询条件
     * @param array|null $orderBy 排序条件
     * @param int|null $limit 限制结果数量
     * @param int|null $offset 跳过指定数量的结果
     * @return array 匹配条件的实体数组
     * @throws QueryException
     */
    public function findBy(Criteria $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
    {
        $qb = $this->createQueryBuilder('e');
        $qb->addCriteria($criteria);

        if ($orderBy) {
            $qb->orderBy($orderBy);
        }

        if ($limit) {
            $qb->setMaxResults($limit);
        }

        if ($offset) {
            $qb->setFirstResult($offset);
        }

        return $qb->getQuery()->getResult();
    }

    /**
     * @param object $model
     * @param array $rows
     * @return void
     * @throws ORMException
     * @throws OptimisticLockException
     */
    public function create(object $model, array $rows): void
    {
        try {
            $this->manager->persist($model);
            foreach ($rows as $field => $value) {
                if (property_exists($model, $field)) {
                    $model->$field = $value;
                } else {
                    throw new \InvalidArgumentException("Invalid property '{$field}' for {$this->className} entity.");
                }
            }
            $this->manager->flush();
        }catch (OptimisticLockException|ORMException $exception){
            throw new $exception;
        }
    }

    /**
     * @param object $model
     * @param array $rows
     * @return void
     * @throws ORMException
     * @throws OptimisticLockException
     */
    public function update(object $model, array $rows): void
    {
        try {
            if (!$model) {
                throw new \InvalidArgumentException("{$this->className} with ID not found.");
            }
            foreach ($rows as $field => $value) {
                if (property_exists($model, $field)) {
                    $model->$field = $value;
                } else {
                    throw new \InvalidArgumentException("Invalid property '{$field}' for {$this->className} entity.");
                }
            }
            $this->manager->flush();
        }catch (OptimisticLockException|ORMException $exception){
            throw new $exception;
        }
    }

    /**
     * 删除实体
     *
     * @param object $entity 要删除的实体对象
     * @return void
     * @throws ORMException
     * @throws OptimisticLockException
     */
    public function delete($entity): void
    {
        $this->manager->remove($entity);
        $this->manager->flush();
    }

    /**
     * 创建 QueryBuilder 实例
     *
     * @param string $alias 实体别名
     * @return QueryBuilder
     */
    public function createQueryBuilder(string $alias = 'e'): QueryBuilder
    {
        return $this->repository->createQueryBuilder($alias);
    }
}